#mockito #java
### Подключение библиотеки Mockito
```
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>5.2.0</version>
        </dependency>
    </dependencies>
```
> TODO Добавить подключение mockito-inline через зависимости и написать что он нужен моков статичных методов.
### Пример создания mock и spy объектов и их вызов
```java
import org.junit.jupiter.api.Test;  
  
import java.util.ArrayList;  
import java.util.List;  
  
import static org.mockito.Mockito.*;  
  
/**  
* Пример создания mock и spy объектов и их вызов
*/
public class MockAndStyExampleTest {  
  
	/**  
	* Создание mock-объекта, необходимо определеить поведение для вызываемого метода
	*/
	@Test
    void mockTest() {  
        // Объект mock создается для класса  
        List<String> listMock = mock(List.class);  
        when(listMock.size()).thenReturn(8);  
  
        listMock.add("el1");  
        listMock.add("el2");  
  
        System.out.println("Вызов mock-метода с результатом: " + listMock.size());  
    }  
  
    /**  
    * Создание spy-объекта для экземпляра класса, переопределение происходит при необходимости получить требуемый
    * результат
    */
    @Test
    void spyTest() {  
        List<String> list = new ArrayList<>();  
        // Объект spy создается для экземпляра класса  
        List<String> spyMock = spy(list);  
  
        // Наполнение объекта реальными значениями  
        spyMock.add("el1");  
        spyMock.add("el2");  
        System.out.println("Вызов реального spy-метода с результатом: " + spyMock.size());  
  
        // Переопределение одного из методов  
        when(spyMock.size()).thenReturn(8);  
        System.out.println("Вызов переопределенного spy-метода с результатом: " + spyMock.size());  
    }  
}
```
### Проверка количества и порядка вызовов методов
```java
import org.junit.jupiter.api.Assertions;  
import org.junit.jupiter.api.Test;  
import org.mockito.ArgumentMatchers;  
import org.mockito.InOrder;  
import org.mockito.exceptions.verification.NoInteractionsWanted;  
  
import java.util.List;  
import java.util.Map;  
  
import static org.mockito.Mockito.*;  
  
/**  
* Проверка количества и порядка вызовов методов
*/
public class MockitoVerifyTest {  
  
    @Test
    void verifyExamplesTest() {  
        List<String> mockList = mock(List.class);  
  
        mockList.add("Test Text");  
        mockList.size();  
  
        // Проверка вызова тестируемого метода с указанным значением параметра  
        verify(mockList).add("Test Text");  
  
        // Проверка вызова тестируемого метода с указанным типом данных  
        verify(mockList).add(anyString());  
        verify(mockList).add(any(String.class));  
        verify(mockList).add(ArgumentMatchers.any(String.class));  
  
        // Типы проверок указанного количества для метода:  
        // Стандартная проверка с указанием количества вызова
        verify(mockList, times(1)).size();  
        // Должен быть вызван минимум один раз  
        verify(mockList, atLeastOnce()).size();  
        // Должен быть вызван не более указанного количества раз  
        verify(mockList, atMost(2)).size();  
        // Должен быть вызван хотя бы указанное количество раз  
        verify(mockList, atLeast(1)).size();  
        // Метод не должен быть вызван  
        verify(mockList, never()).clear();  
        // Вызов проверки после указанного ожидания (в мс.)  
        verify(mockList, timeout(100)).size();  
    }  
  
    /**  
    * Провекрка вызова только одного метода. Пример вынесен в отдельный класс для демонстрации работы, т.к.
    * в методе {@link #verifyExamplesTest()} не удавалось продемонстрировать его работу.
    */
    @Test
    void onlyExampleTest() {  
        List<String> mockList = mock(List.class);  
//        mockList.add("Test Text"); // При вызове метода, отличного от проверяемого будет показана ошибка  
        mockList.size();  
  
        // Проверить что вызван только указанный метод  
        verify(mockList, only()).size();  
    }  
  
    /**  
    * Проверить что все взаимодействия с замоканным объектом были проверены.
    */
	@Test
    void verifyNoMoreInteractionsFailTest() {  
        List<String> mockList = mock(List.class);  
        mockList.isEmpty();  
  
        // Вызов тестируемого метода  
        // Метод isEmpty() был вызван в mock объекте, но вызов не был проверен - будет показана ошибка
        NoInteractionsWanted noInteractionsWanted = Assertions.assertThrows(NoInteractionsWanted.class, () -> verifyNoMoreInteractions(mockList));  
        // Проверка соответствия сообщения об ошибке  
        Assertions.assertTrue(noInteractionsWanted.getLocalizedMessage().contains("No interactions wanted here"));  
    }  
  
    /**  
    * Проверка порядка вызова методов разных объектов
    */
    @Test
    void inOrderTest() {  
        // Объявление заглушек классов  
        List<String> mockList = mock(List.class);  
        Map<String, String> mockMap = mock(Map.class);  
  
        // Вызов проверяемых методов  
        mockList.add("Test Text");  
        mockList.size();  
        mockList.isEmpty();  
        mockMap.isEmpty();  
  
        // Проверка порядка вызовов  
        InOrder inOrder = inOrder(mockList, mockMap);  
        inOrder.verify(mockList).add(anyString());  
        inOrder.verify(mockList).size();  
        // Можно проверять порядок только необходимых вызовов методов, а ненужные пропускать  
//        inOrder.verify(mockList).isEmpty();  
        inOrder.verify(mockMap).isEmpty();  
    }  
}
```
### Создание заглушек для статичных методов
```java
import org.junit.jupiter.api.Assertions;  
import org.junit.jupiter.api.Test;  
import org.mockito.MockedStatic;  
import org.mockito.invocation.InvocationOnMock;  
import org.mockito.stubbing.Answer;  
  
import java.time.LocalDateTime;  
  
import static org.mockito.ArgumentMatchers.anyString;  
import static org.mockito.Mockito.*;  
import static org.mockito.internal.verification.VerificationModeFactory.times;  
  
/**  
* Создание заглушек для статичных методов
*/
public class MockitoStaticMethodsTest {  
  
    /**  
    * Пример создания заглушки для void статичного метода класса.
    */
    @Test
    void callVoidMethodOneTest() {  
        MockedStatic<TestClassStatic> mock = mockStatic(TestClassStatic.class);  
        // Создание заглушки статичного класса  
        mock  
                // Объявление метода, для которого создается заглушка  
                .when(() -> TestClassStatic.print(anyString()))  
                // Настройка ответа для вызванного метода  
                .thenAnswer((Answer<Void>) invocation -> {  
                            // Получение списка переданных в метод параметров  
                            Object[] args = invocation.getArguments();  
                            String param = args[0].toString();  
                            System.out.println("Вызван замокированный метод с параметром: " + param);  
                            return null;  
                        }  
                );  
  
        // Вызов тестируемого метода  
        TestClassStatic.print("Test Text");  
  
        // Проверка вызова метода с требуемыми параметрами  
        mock.verify(() -> TestClassStatic.print("Test Text"), times(1));  
  
        // Завершение работы заглушки  
        mock.close();  
    }  
  
    /**  
    * Вариация метода {@link #callVoidMethodOneTest()}, рекомендованный способ создания заглушки статичного класса
    */
    @Test
    void callVoidMethodTwoTest() {  
        // Создание заглушки статичного класса  
        try (MockedStatic<TestClassStatic> mock = mockStatic(TestClassStatic.class)) {  
            mock  
                    // Объявление метода, для которого создается заглушка  
                    .when(() -> TestClassStatic.print(anyString()))  
                    // Настройка ответа для вызванного метода  
                    .thenAnswer((Answer<Void>) invocation -> {  
                        // Получение списка переданных в метод параметров  
                        Object[] args = invocation.getArguments();  
                        String param = args[0].toString();  
                        System.out.println("Вызван замокированный метод с параметром: " + param);  
                        return null;  
                    });  
  
            // Вызов тестируемого метода  
            TestClassStatic.print("Test Text");  
  
            // Проверка вызова метода с требуемыми параметрами  
            mock.verify(() -> TestClassStatic.print("Test Text"), times(1));  
        }  
    }  
  
    /**  
	* Вариация метода {@link #callVoidMethodOneTest()} с развернутым созданием объектов
	*/  
	@Test
    void callVoidMethodThreeTest() {  
        MockedStatic<TestClassStatic> mock = mockStatic(TestClassStatic.class);  
  
        // Создание заглушки статичного класса  
        mock  
                // Объявление метода, для которого создается заглушка  
                .when(new MockedStatic.Verification() {  
                    @Override  
                    public void apply() throws Throwable {  
                        System.out.println("Настройка метода для возврата значения");  
                        TestClassStatic.print(anyString());  
                    }  
                })  
                // Настройка ответа для вызванного метода  
                .thenAnswer(new Answer<Void>() {  
                                public Void answer(InvocationOnMock invocation) throws Throwable {  
                                    Object[] args = invocation.getArguments();  
                                    String param = args[0].toString();  
                                    System.out.println("Вызван замокированный метод с параметром: " + param);  
                                    return null;  
                                }  
                            }  
                );  
  
        // Вызов тестируемого метода  
        TestClassStatic.print("Test Text");  
  
        // Проверка вызова метода с требуемыми параметрами  
        mock.verify(() -> TestClassStatic.print("Test Text"), times(1));  
  
        // Завершение работы заглушки  
        mock.close();  
    }  
  
    /**  
    * Создание заглушки статичного метода класса с возвратом параметров
    */
    @Test
    void callMethodWithReturnParamTest() {  
        MockedStatic<TestClassStatic> mock = mockStatic(TestClassStatic.class);  
  
        // Создание заглушки статичного класса  
        mock  
                // Объявление метода, для которого создается заглушка  
                .when(() -> TestClassStatic.getErrorMessage(anyString()))  
                // Настройка ответа для вызванного метода  
                .thenAnswer((Answer<String>) invocation -> {  
                            // Получение списка переданных в метод параметров  
                            Object[] args = invocation.getArguments();  
                            String param = args[0].toString();  
                            System.out.println("Вызван замокированный метод с параметром: " + param);  
                            return "New Test Value";  
                        }  
                );  
  
        // Вызов тестируемого метода  
        Assertions.assertEquals("New Test Value", TestClassStatic.getErrorMessage("Test Text"));  
  
        // Проверка вызова метода с требуемыми параметрами  
        mock.verify(() -> TestClassStatic.getErrorMessage("Test Text"), times(1));  
  
        // Завершение работы заглушки  
        mock.close();  
    }  
  
    /**  
    * Пример возврата класса-заглушки mockLocalDateTime.getMinute в статичном методе класса LocalDateTime.now
    */
    @Test
    void anotherExampleCallStaticTest() {  
        try (MockedStatic<LocalDateTime> mocked = mockStatic(LocalDateTime.class)) {  
            var mockLocalDateTime = mock(LocalDateTime.class);  
            when(mockLocalDateTime.getMinute()).thenReturn(30);  
            mocked.when(LocalDateTime::now).thenReturn(mockLocalDateTime);  
  
            LocalDateTime result = LocalDateTime.now();  
  
            Assertions.assertEquals(30, result.getMinute());  
        }  
    }  
  
     /**  
     * Тестовый класс для демонстрации заглушек статичных методов
     */
     private final static class TestClassStatic {  
        public static void print(String text) {  
            System.out.println("Текст для вывода: " + text);  
        }  
  
        public static String getErrorMessage(String message) {  
            System.out.println("Сообщение ошибки: " + message);  
            return "Сообщение ошибки: " + message;  
        }  
    }  
}
```
### Создение заглушки для последующего внутреннего вызова в другом классе
```java
import org.junit.jupiter.api.Assertions;  
import org.junit.jupiter.api.Test;  
import org.mockito.MockedConstruction;  
import org.mockito.stubbing.Answer;  
  
import java.util.List;  
  
import static org.mockito.Mockito.mockConstruction;  
import static org.mockito.Mockito.when;  
  
/**  
* Создение заглушки для последующего внутреннего вызова в другом классе
*/
public class MockConstructionTest {  
  
    /**  
    * Создание заглушки экземпляра класса для внутреннего вызова в другом класса без явного указания заглушки
    */
    @Test
    void mockingConstructor() {  
        // Создание заглушки класса Cat для вызова в другом классе Sound  
        try (MockedConstruction<Cat> mock = mockConstruction(Cat.class, new MockedConstruction.MockInitializer() {  
            @Override  
            public void prepare(Object mock, MockedConstruction.Context context) throws Throwable {  
                // Получение параметра конструктора объекта Cat  
                List<?> args = context.arguments();  
                String param = args.get(1).toString();  
                // Проверка значения параметра  
                Assertions.assertEquals("CALL_OBJECT", param);  
  
                // Настройка данных для возврата в заглушке  
                when(((Cat) mock).makeSound())  
                        // Возврат замоканного значения метода makeSound()  
                        .thenAnswer((Answer<String>) invocation -> {  
                            System.out.println("Вызван метод заглушки makeSound() с результатом 'MOCKED_OBJECT'");  
                            return "MOCKED_OBJECT";  
                        });  
            }  
        })) {  
  
            // Создание базового объекта Sound, в котором будет создан целевой замоканый объект Cat  
            Sound sound = new Sound("CALL_OBJECT");  
            Assertions.assertEquals("MOCKED_OBJECT", sound.play());  
  
            // Получение количества созданных экземпляров целевого класса  
            List<Cat> constructed = mock.constructed();  
            // Проверка количества созданных объектов  
            Assertions.assertEquals(1, constructed.size());  
        }  
  
        // Вызов класса Sound с реальным незамоканным объектом Cat  
        Assertions.assertEquals("REAL_OBJECT", new Sound("REAL_OBJECT").play());  
    }  
  
    /**  
    * Класс демонстрации работы создания заглушки для класса, используемого внутри другого класса
    */
    class Cat {  
        private String name;  
  
        public Cat() {  
            this("DEFAULT_NAME");  
            System.out.println("Был вызван реальный дефолтный конструктор объекта Cat");  
        }  
  
        public Cat(String name) {  
            System.out.println("Вызван реальный конструктор объекта Cat с параметром: " + name);  
            this.name = name;  
        }  
  
        public String makeSound() {  
            System.out.println("Вызван реальный метод makeSound() реального объекта: " + name);  
            return name;  
        }  
    }  
  
     /**  
     * Базовый объект, в котором создается и вызывается метод замоканного объекта
     */
     class Sound {  
        private Cat cat;  
  
        Sound(String name) {  
            System.out.println("Вызван реальный конструктор объекта Sound с параметром: " + name);  
            this.cat = new Cat(name);  
        }  
  
        String play() {  
            System.out.println("Вызван реальный метод play() объекта Sound");  
            return cat.makeSound();  
        }  
    }  
}
```
### Проброс исключений при вызове метода заглушенного объекта
```java
import org.junit.jupiter.api.Assertions;  
import org.junit.jupiter.api.Test;  
import org.mockito.MockedStatic;  
  
import java.time.LocalDateTime;  
import java.util.List;  
  
import static org.mockito.Mockito.*;  
  
/**  
* Проброс исключений при вызове метода заглушенного объекта
*/
public class MockitoThrowExceptionTest {  
  
    /**  
    * Первый способ записи объявления замоканного объекта с пробросом исключения
    */
    @Test
    void throwExceptionOneTest() {  
        // Создание заглушки  
        List<String> mockList = mock(List.class);  
        // Настройка проброса исключения при вызове метода  
        doThrow(new IllegalStateException("Throw Error from Mockito")).when(mockList).size();  
  
        // Вызов тестируемого метода  
        IllegalStateException illegalStateException = Assertions.assertThrows(IllegalStateException.class, () -> mockList.size());  
        // Проверка соответствия сообщения об ошибке  
        Assertions.assertEquals("Throw Error from Mockito", illegalStateException.getMessage());  
    }  
  
    /**  
    * Второй способ записи объявления замоканного объекта с пробросом исключения, функционально аналогичен первому
    */
    @Test
    void throwExceptionTwoTest() {  
        // Создание заглушки  
        List<String> mockList = mock(List.class);  
        // Настройка проброса исключения при вызове метода  
        when(mockList.size()).thenThrow(new IllegalStateException("Throw Error from Mockito"));  
  
        // Вызов тестируемого метода  
        IllegalStateException illegalStateException = Assertions.assertThrows(IllegalStateException.class,  
                () -> mockList.size());  
        // Проверка соответствия сообщения об ошибке  
        Assertions.assertEquals("Throw Error from Mockito", illegalStateException.getMessage());  
    }  
  
    /**  
    * Пример возврата исключения из класса-заглушки mockLocalDateTime.getMinute после выхова статичного метода
    * класса LocalDateTime.now
    */
    @Test
    void anotherExampleCallStaticTest() {  
        // Создание заглушки статичного класса  
        try (MockedStatic<LocalDateTime> mocked = mockStatic(LocalDateTime.class)) {  
            // Создание дополнительной заглушки дополнительного класса  
            LocalDateTime mockLocalDateTime = mock(LocalDateTime.class);  
            // Настройка проброса исключения при вызове метода дополнительного класса  
            when(mockLocalDateTime.getMinute()).thenThrow(new IllegalStateException("Throw Error from Mockito"));  
            // Настройка возврата статичным методом дополнительного класса  
            mocked.when(LocalDateTime::now).thenReturn(mockLocalDateTime);  
  
            // Вызов статичного метода класса  
            LocalDateTime result = LocalDateTime.now();  
  
            // Вызов тестируемого метода  
            IllegalStateException illegalStateException = Assertions.assertThrows(IllegalStateException.class,  
                    () -> result.getMinute());  
            // Проверка соответствия сообщения об ошибке  
            Assertions.assertEquals("Throw Error from Mockito", illegalStateException.getMessage());  
        }  
    }  
}
```
### Ссылки
[Mockito Tutorial (tutorialspoint.com)](https://www.tutorialspoint.com/mockito/index.htm)